import { EventTouch, Node, Vec3, tween } from 'cc'

/** 拖拽动画器的初始化参数 */
export interface DragAnimatorOptions {
	/** 需要进行拖拽的节点 */
	node: Node
	/** 起始位置 */
	startPos: Vec3
	/** 目标位置 */
	targetPos: Vec3
	/** 动画持续时间(秒)，默认0.3秒 */
	animDuration?: number
	/** 是否启用拖拽，默认true */
	dragEnabled?: boolean
	/** 各类回调函数 */
	callbacks?: DragAnimatorCallbacks
}

/** 拖拽动画器的回调函数集合 */
export interface DragAnimatorCallbacks {
	/** 开始拖拽时的回调 */
	onDragStart?: () => void
	/** 拖拽过程中的回调，progress为0-1之间的进度值 */
	onDragProgress?: (progress: number) => void
	/** 拖拽结束时的回调，isToTarget为true表示移动到目标位置，false表示回到起始位置 */
	onDragComplete?: (isToTarget: boolean) => void
}

/**
 * 拖拽动画控制器
 * 用于处理节点的垂直拖拽和自动回弹动画
 */
export class DragAnimator {
	private node: Node
	private startPos: Vec3
	private targetPos: Vec3
	private isDragging: boolean = false
	private isSwipingUp: boolean = false
	private touchStartPos: Vec3 = new Vec3()
	/** 存储最近几次移动的距离，用于计算平均移动方向 */
	private lastFewDeltas: number[] = []
	/** 计算平均移动方向时采样的次数 */
	private readonly DELTA_SAMPLE_COUNT = 20
	/** 动画过渡时间 */
	private animDuration: number
	/** 回调函数集合 */
	private callbacks: DragAnimatorCallbacks
	/** 是否启用拖拽 */
	private dragEnabled: boolean

	constructor(options: DragAnimatorOptions) {
		this.node = options.node
		this.startPos = options.startPos.clone()
		this.targetPos = options.targetPos.clone()
		this.animDuration = options.animDuration || 0.3
		this.callbacks = options.callbacks || {}
		this.dragEnabled = options.dragEnabled ?? true

		this.node.position = this.startPos
		this.registerEvents()
	}
	/** 设置拖拽的起始位置和目标位置 */
	setPosRange(start: Vec3, target: Vec3) {
		this.startPos = start.clone()
		this.targetPos = target.clone()
		this.node.position = this.startPos
	}

	/** 设置是否启用拖拽 */
	setDragEnabled(enabled: boolean) {
		this.dragEnabled = enabled
	}
	/**
	 * 执行移动动画
	 * @param isToTarget true移动到目标位置，false移动到起始位置
	 */
	playAnimation(isToTarget: boolean) {
		const targetPosition = isToTarget ? this.targetPos : this.startPos
		const totalDistance = Math.abs(this.targetPos.y - this.startPos.y)
		const remainingDistance = Math.abs(this.node.position.y - targetPosition.y)
		const adjustedDuration = this.animDuration * (remainingDistance / totalDistance)

		tween(this.node)
			.to(
				adjustedDuration,
				{ position: targetPosition },
				{
					easing: 'quadOut',
					onUpdate: () => this.callbacks.onDragProgress?.(this.getProgress()),
					onComplete: () => this.callbacks.onDragComplete?.(isToTarget)
				}
			)
			.start()
	}

	/** 注册触摸事件 */
	private registerEvents() {
		this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this)
		this.node.on(Node.EventType.TOUCH_MOVE, this.onTouchMove, this)
		this.node.on(Node.EventType.TOUCH_END, this.onTouchEnd, this)
		this.node.on(Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this)
	}

	/** 注销触摸事件 */
	private unregisterEvents() {
		this.node.off(Node.EventType.TOUCH_START, this.onTouchStart, this)
		this.node.off(Node.EventType.TOUCH_MOVE, this.onTouchMove, this)
		this.node.off(Node.EventType.TOUCH_END, this.onTouchEnd, this)
		this.node.off(Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this)
	}

	/** 处理触摸开始事件 */
	private onTouchStart(event: EventTouch) {
		if (!this.dragEnabled) return

		this.isDragging = true
		this.lastFewDeltas = []

		const location = event.getUILocation()
		this.touchStartPos.set(location.x, location.y, 0)
		this.callbacks.onDragStart?.()
	}

	/** 处理触摸移动事件 */
	private onTouchMove(event: EventTouch) {
		if (!this.dragEnabled || !this.isDragging) return

		const location = event.getUILocation()
		const delta = new Vec3(0, location.y - this.touchStartPos.y, 0)

		this.lastFewDeltas.push(delta.y)
		if (this.lastFewDeltas.length > this.DELTA_SAMPLE_COUNT) {
			this.lastFewDeltas.shift()
		}

		const averageDelta = this.lastFewDeltas.reduce((a, b) => a + b, 0) / this.lastFewDeltas.length
		this.isSwipingUp = averageDelta > 0

		const newPos = this.node.position.clone().add(delta)
		newPos.y = Math.max(
			Math.min(newPos.y, Math.max(this.startPos.y, this.targetPos.y)),
			Math.min(this.startPos.y, this.targetPos.y)
		)

		this.node.position = newPos
		this.touchStartPos.set(location.x, location.y, 0)
		this.callbacks.onDragProgress?.(this.getProgress())
	}

	/** 处理触摸结束事件 */
	private onTouchEnd() {
		if (!this.dragEnabled || !this.isDragging) return
		this.isDragging = false

		const targetPosition = this.isSwipingUp ? this.targetPos : this.startPos
		const totalDistance = Math.abs(this.targetPos.y - this.startPos.y)
		const remainingDistance = Math.abs(this.node.position.y - targetPosition.y)
		const adjustedDuration = this.animDuration * (remainingDistance / totalDistance)

		tween(this.node)
			.to(
				adjustedDuration,
				{ position: targetPosition },
				{
					easing: 'quadOut',
					onUpdate: () => this.callbacks.onDragProgress?.(this.getProgress()),
					onComplete: () => this.callbacks.onDragComplete?.(this.isSwipingUp)
				}
			)
			.start()
	}

	/** 获取当前进度(0-1) */
	private getProgress(): number {
		return (this.node.position.y - this.startPos.y) / (this.targetPos.y - this.startPos.y)
	}

	/** 更新回调函数 */
	setCallbacks(callbacks: DragAnimatorCallbacks) {
		this.callbacks = callbacks
	}

	/** 销毁时清理事件 */
	destroy() {
		this.unregisterEvents()
	}
}
